package itdep.jsf.tag;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.el.VariableMapperWrapper;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;
import com.sun.facelets.tag.ui.CompositionHandler;
import com.sun.facelets.tag.ui.DecorateHandler;
import com.sun.facelets.tag.ui.IncludeHandler;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ReflectionUtils;

import javax.el.ELException;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/** Created: 23.04.2009 18:28:40 by Tolar */

/**
 * Prevents variable resolving from parent context.
 * <p/>
 * Facelets' {@link CompositionHandler}, {@link DecorateHandler}, and
 * {@link IncludeHandler} use {@link VariableMapperWrapper} for wrapping
 * parent's {@link VariableMapper}. If {@link VariableMapperWrapper} doesn't have
 * variable expression it delegates to parent mapper. This class is responsible for
 * preventing variable resolving from parent mapper.
 * <p/>
 * Can be child of {@link CompositionHandler}, {@link DecorateHandler},
 * and {@link IncludeHandler} handlers.
 *
 * @since 2.0
 */
public class DoNotInheritVarsTag extends TagHandler {
	private static final Log log = LogFactory.getLog(DoNotInheritVarsTag.class);

	private TagAttribute vars;

	private Set<String> doNotIngeritedVars;

	public DoNotInheritVarsTag(final TagConfig config) {
		super(config);
		vars = getRequiredAttribute("vars");
		initDoNotInheritableVars();
	}

	private void initDoNotInheritableVars() {
		doNotIngeritedVars = new HashSet<String>();

		final String value = vars.getValue();

		if (StringUtils.isNotEmpty(value)) {
			final String[] wars = StringUtils.split(value, ",");

			doNotIngeritedVars.addAll(Arrays.asList(wars));
		}
	}

	@SuppressWarnings("unchecked")
	public void apply(final FaceletContext ctx, final UIComponent parent)
			throws FacesException, ELException, IOException {
		final VariableMapper variableMapper = ctx.getVariableMapper();

		if (variableMapper instanceof VariableMapperWrapper) {
			final Field varsField = ReflectionUtils.findField(VariableMapperWrapper.class, "vars", Map.class);

			if (varsField == null) {
				log.warn("VariableMapperWrapper's vars haven't been found.");
			} else {
				varsField.setAccessible(true);

				try {
					final Map<String, ValueExpression> map =
							(Map<String, ValueExpression>) varsField.get(variableMapper);

					for (String var : doNotIngeritedVars) {
						if (!map.containsKey(var)) {
							map.put(var, ctx.getExpressionFactory().createValueExpression(null, String.class));
						}
					}

				} catch (IllegalArgumentException ex) {
					log.error(ex.getMessage(), ex);
				} catch (IllegalAccessException ex) {
					log.error(ex.getMessage(), ex);
				}
			}
		}

		nextHandler.apply(ctx, parent);
	}
}
